/* 
 * Copyright (C) 2004-2005 Jonathan Bindel
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Collections;
using System.Collections.Generic;

using DCSharp.Backend.Connections;
using DCSharp.Backend.Objects;
using DCSharp.Settings;

namespace DCSharp.Backend.Managers
{
	/// <summary>
	/// Responsible for creating new hub connections and maintaining a list of
	/// all the connected hubs.
	/// </summary>
	public class HubManager : IEnumerable<HubConnection>
	{
		public event EventHandler<ConnectionEventArgs> ConnectionCreated;
		public event EventHandler<ConnectionEventArgs> HubConnected;
		public event EventHandler<ConnectionEventArgs> HubDisconnected;

		private ProtocolHelper helper;
		private IRuntimeSettings settings;

		private List<HubConnection> hubs;

		public HubManager(ProtocolHelper helper, IRuntimeSettings settings)
		{
			if (helper == null)
			{
				throw new ArgumentNullException("helper");
			}
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			this.helper = helper;
			this.settings = settings;

			hubs = new List<HubConnection>();
		}

		#region Properties

		/// <value>
		/// The number of connected hubs.
		/// </value>
		public int Count
		{
			get { return hubs.Count; }
		}

		public HubConnection[] Hubs
		{
			get
			{
				lock (hubs)
				{
					return hubs.ToArray();
				}
			}
		}

		public object SyncRoot
		{
			get { return hubs; }
		}

		#endregion

		#region Methods

		#region Connect

		public HubConnection ConnectTo(Uri uri)
		{
			LocalIdentity identity = settings.LocalIdentity;
			string password = null;

			string userInfo = uri.UserInfo;
			if (userInfo != String.Empty)
			{
				int i = userInfo.IndexOf(':');
				if (i > 0)
				{
					identity = new LocalIdentity(identity.Pid,
						userInfo.Substring(0, i));
					password = userInfo.Substring(++i, userInfo.Length - i);
				}
				else
				{
					identity = new LocalIdentity(identity.Pid, userInfo);
				}
			}
			return ConnectTo(uri, identity, password);
		}

		public HubConnection ConnectTo(Uri uri, LocalIdentity identity,
			string password)
		{
			if (uri == null)
			{
				throw new ArgumentNullException("uri");
			}
			if (identity == null)
			{
				throw new ArgumentNullException("identity");
			}

			// TODO: Strip the user information from the uri.
			HubConnection hub = GetHub(uri);
			if (hub == null)
			{
				hub = new HubConnection(uri, identity, helper);
				hub.Password = password;
				hub.StateChanged += HandleHubStateChanged;

				OnConnectionCreated(hub);
				hub.Connect();
			}
			return hub;
		}

		private void HandleHubStateChanged(object obj, EventArgs args)
		{
			HubConnection hub = (HubConnection)obj;
			if (hub.State == ConnectionState.Connected)
			{
				lock (hubs)
				{
					if (hubs.Contains(hub))
					{
						return;
					}
					hubs.Add(hub);
				}
				OnHubConnected(hub);
			}
			else if (hub.State == ConnectionState.Disconnected)
			{
				hub.StateChanged -= HandleHubStateChanged;
				lock (hubs)
				{
					if (!hubs.Contains(hub))
					{
						return;
					}
					hubs.Remove(hub);
				}
				OnHubDisconnected(hub);
			}
		}

		#endregion

		public bool Contains(HubConnection hub)
		{
			return hubs.Contains(hub);
		}

		public HubConnection Find(Predicate<HubConnection> match)
		{
			if (match == null)
			{
				throw new ArgumentNullException("match");
			}
			lock (hubs)
			{
				return hubs.Find(match);
			}
		}

		public void ForEach(Action<HubConnection> action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			lock (hubs)
			{
				hubs.ForEach(action);
			}
		}

		public IEnumerator<HubConnection> GetEnumerator()
		{
			return hubs.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		public HubConnection GetHub(Uri uri)
		{
			return Find(delegate(HubConnection hub)
			{
				return hub.Uri == uri;
			});
		}

		public HubConnection GetHubWithUser(User user)
		{
			return Find(delegate(HubConnection hub)
			{
				return user.IsOnlineOnHub(hub);
			});
		}

		public bool TryGetHub(string uriString, out HubConnection hub)
		{
			hub = null;

			Uri uri;
			if (StringUtil.TryCreateUri(uriString, out uri))
			{
				hub = GetHub(uri);
			}
			return hub != null;
		}

		protected virtual void OnConnectionCreated(HubConnection hub)
		{
			if (ConnectionCreated != null)
			{
				ConnectionCreated(null, new ConnectionEventArgs(hub));
			}
		}

		protected virtual void OnHubConnected(HubConnection hub)
		{
			if (HubConnected != null)
			{
				HubConnected(this, new ConnectionEventArgs(hub));
			}
		}

		protected virtual void OnHubDisconnected(HubConnection hub)
		{
			if (HubDisconnected != null)
			{
				HubDisconnected(this, new ConnectionEventArgs(hub));
			}
		}

		#endregion
	}
}
